//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// Positioning stuff.
//

# include "StdAfx.h"
# include "Positioning.h"
# include "Tracking.h"
# include "GpsPanel.h"
# include "ComPort.h"
# include "GpsProtocol.h"
# include "NMEA0183.h"
# include "GarminProtocol.h"
# include "Globals.h"

static string_t g_strStatus;
static PositionInfo_t g_pi;
static CCritSec g_csPI;

void SetPositioningStatus (const string_t & _strStatus) {
	{
		CCritSec::CLock lock (g_csPI);
		g_strStatus = _strStatus;
	}
	UpdateGPSPanel ();
}

void GetPositioningStatus (string_t & _ret) {
	CCritSec::CLock lock (g_csPI);
	_ret = g_strStatus;
}

void PushPositionInfo (const PositionInfo_t & _pi) {
	bool bAddPointToTrack = false;
	{
		CCritSec::CLock lock (g_csPI);

		if (
			(g_pi.dwDef & PositionInfo_t::defXY) &&
			(_pi.dwDef  & PositionInfo_t::defXY)
		) {
			float fXPrec = 0, fYPrec = 0;
			if (_pi.dwDef & PositionInfo_t::defXYPrec) {
				fYPrec = 360.*_pi.fXYPrec/40e6;
				fXPrec = fYPrec/::cos (_pi.fY*c_PI/180.f);
			}

			if (
				::fabs (g_pi.fX - _pi.fX) > fXPrec &&
				::fabs (g_pi.fY - _pi.fY) > fYPrec
			)
				bAddPointToTrack = true;
		} else if (
			(g_pi.dwDef & PositionInfo_t::defXY) == 0 &&
			(_pi.dwDef  & PositionInfo_t::defXY) != 0
		)
			// TODO: start new track
			bAddPointToTrack = true;

		g_pi = _pi;
	}
	UpdateGPSPanel ();

	if (bAddPointToTrack)
		AddPointToTrack (_pi);
}

void PushNoPositionInfo () {
	StartNewTrack ();
}

PositionInfo_t GetPositionInfo () {
	CCritSec::CLock lock (g_csPI);
	return g_pi;
}

/////////////////////////////////////////////

static bool g_bStopCom = true;
static std::set<HANDLE> g_hComThreads;
static HANDLE g_hStopEvent = NULL;
static HANDLE g_hReadyEvent = NULL;
static LONG g_lThreads;

static
DWORD WINAPI ComThreadProc (LPVOID _lpParameter) {
	//
	// Open COM port.
	//
	const size_t cComPort = reinterpret_cast<size_t> (_lpParameter);
	assert (1 <= cComPort && cComPort <= 19);
	char strComPort [12];
	if (1 <= cComPort && cComPort <= 9)
		::_snprintf (strComPort, ARRAY_SIZE (strComPort) - 1, "COM%d", cComPort);
	else if (10 <= cComPort && cComPort <= 99)
		::_snprintf (strComPort, ARRAY_SIZE (strComPort) - 1, "\\\\.\\COM%d", cComPort);
	else
		assert (false);

	CComPort port (strComPort);
	if (! port.IsValid ()) {
		if (::InterlockedDecrement (& g_lThreads) == 0)
			SetPositioningStatus ("Unable to open any COM port.");
		return 1;
	}

	//
	// Try protocols.
	//
	CNMEA0183 NMEA_protocol;
	CGarminProtocol Garmin_protocol;
	CGpsProtocol * const pProtocols [] = {& Garmin_protocol, & NMEA_protocol};
	const size_t cProtocols = sizeof (pProtocols)/sizeof (pProtocols [0]);

	size_t cProtocolIndex = 0;
	while (! g_bStopCom) {
		assert (cProtocolIndex < cProtocols);
		CGpsProtocol * const pProtocol = pProtocols [cProtocolIndex];
		++ cProtocolIndex;
		cProtocolIndex %= cProtocols;

		const bool bExit = pProtocol->Run (& port, cComPort, g_hStopEvent, g_hReadyEvent);
		if (bExit)
			break;

		PushNoPositionInfo ();
	}

	::InterlockedDecrement (& g_lThreads);
	return 0;
}

void StartGPSUnitCommunication () {
	StopGPSUnitCommunication ();

	if (g_hReadyEvent == NULL)
		g_hReadyEvent = ::CreateEvent (NULL, TRUE, FALSE, NULL);
	::ResetEvent (g_hReadyEvent);

	if (g_hStopEvent == NULL)
		g_hStopEvent = ::CreateEvent (NULL, TRUE, FALSE, NULL);
	::ResetEvent (g_hStopEvent);
	g_bStopCom = false;

	g_lThreads = 0;
	for (size_t cPort = 1; cPort <= 19; ++ cPort) {
		::InterlockedIncrement (& g_lThreads);

		DWORD dwId = 0;
		g_hComThreads.insert (::CreateThread (NULL, 0, ComThreadProc, reinterpret_cast<LPVOID> (cPort), 0, & dwId));
	}
}

void StopGPSUnitCommunication () {
	g_bStopCom = true;
	if (g_hStopEvent)
		::SetEvent (g_hStopEvent);

	if (! g_hComThreads.empty ()) {
		const size_t cThreads = g_hComThreads.size ();
		HANDLE * const pThreads = (HANDLE *) alloca (sizeof (HANDLE)*cThreads);
		std::set<HANDLE>::iterator i;
		size_t c = 0;
		for (i = g_hComThreads.begin (); i != g_hComThreads.end (); ++ i)
			pThreads [c ++] = * i;

		::WaitForMultipleObjects (cThreads, pThreads, TRUE, 2000);

		for (i = g_hComThreads.begin (); i != g_hComThreads.end (); ++ i)
			::CloseHandle (* i);
		g_hComThreads.clear ();
	}
}

bool IsGPSUnitActive () {
	return ! g_bStopCom;
}
